# Copyright (c) 2005,2006,2007 Dynamic Solutions Inc. (support@dynamic-solutions.com)
#
# This file is part of DataZoomer.
#
# DataZoomer is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# DataZoomer is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""Manage environment and application manager

The dzManager class uses the singleton (or Borg) pattern
to ensure there is only ever one instance of the
manager.
"""


import sys

from zoom import zoomer
from dzacl import Acl
from dzsystem import dzWebVars
from dzresponse import HTMLResponse, ExceptionResponse

from time import time
from StringIO import StringIO
from os import listdir, chdir
from os.path import isdir, join

class Manager:

    def __init__(self):
        """ Decide what theme to use
            This part needs some more thought.
            I would prefer that the zoomer object not know about the theme
            but it's so darn efficient to have one theme and all of the
            potential apps using it, so here it is.
        """
#        zoomer.set_theme(zoomer.theme_name)

    def respond(self,webvars):
        """ The respond() method must return a fully formed http response document complete with header
            information.  It delegates this activity to the run() method.

            Run the application
        """
        zoomer.initialize_connection_state()
        doc = self.run_app(None,webvars)
        return doc

    def load_app(self,name):
        return zoomer.load_app(name)

    def installed_apps(self):
        installed = []
        for path in zoomer.app_paths:
            app_names = [name.lower() for name in listdir(path) if name[0]!='.' and isdir(join(path,name))]
            for name in app_names:
                if not name in installed:
                    installed.append(name)
        return installed

    def get_user_applications(self):
        import dzapp

        if zoomer.admin_group in zoomer.user_groups:
            app_names = self.installed_apps()
        else:
            app_names = [name for name in zoomer.appnames if 'a_'+name in zoomer.user_groups]


        # attempt to load every application
        result = []
        for name in app_names:
            app = dzapp.App(name)
            zoomer.apps[name] = app
            if app:
                result.append(app)
            else:
                zoomer.debug('Error loading app %s' % name)
        result.sort(lambda a,b: cmp(a.position,b.position))
        return result

    def get_system_applications(self):
        def include_app(app_name,authenticated):
            return authenticated and not app_name in ['login','register'] or not authenticated and not app_name in ['logout']
        return [app for app in zoomer.user_apps if app.visible and app.system_app and include_app(app.name,zoomer.authenticated)]

    def reload_applications(self):
        self.user_apps = self.get_user_applications()

    def run_app(self,app,webvars=dzWebVars()):

        # Make web variables available
        zoomer.webvars = webvars

        # Start the system timers for this job
        self.start_time = time()
        zoomer.debug.start_time = self.start_time
        zoomer.trace('start')

        # Get the session ID
        #zoomer.use_cookies = 1
        if zoomer.use_cookies:
            import Cookie, os
            session_cookie_name = 'dzsid'
            ck = Cookie.SimpleCookie(os.environ.get("HTTP_COOKIE", ""))
            if session_cookie_name in ck:
                sid = ck[session_cookie_name].value
                zoomer.debug('sid is %s'%sid)
            else:
                sid = ''
        else:
            sid = webvars.sid

        # Load session variables
        if not zoomer.session.load_session(sid):
            sid = zoomer.session.new_session()

        zoomer.sid = sid

        # Identify user and which apps they can see
        #anonymous_user = 'anonymous'
        anonymous_user = zoomer.default_user
        if not zoomer.session.loginid:
            zoomer.session.loginid = ((not zoomer.ntuser or zoomer.ntusername==None) and anonymous_user or zoomer.ntusername)
        zoomer.loginid = zoomer.session.loginid
        zoomer.authenticated = (zoomer.loginid != anonymous_user)

        zoomer.acl = Acl(zoomer.db)
        # is there no loginid record
        if zoomer.ntuser:
            rec_found = zoomer.acl.is_user(zoomer.session.loginid)
            if not rec_found and not zoomer.webvars.app=='profile':
                url = '%s?sid=%s&app=profile&action=add' % (zoomer.main_url,zoomer.sid)
                #lets save the original url to redirect after
                args=[]
                for var in zoomer.webvars.__dict__.keys():
                    if not var == '_dzCgiWebVars__f' and not var == 'sid':
                        args.append('%s=%s' % (var,str(webvars.__dict__[var])))
                zoomer.session.original_url = '%s?%s&amp;sid=%s' % (zoomer.main_url,'&amp;'.join(args),zoomer.sid)
                zoomer.session.save_session(zoomer.sid)
                return redirect_to(url)
            if rec_found:
                zoomer.session.userid = zoomer.acl.get_user_id(zoomer.loginid)
                zoomer.session.email = ''
            zoomer.user_groups = zoomer.acl.get_groups(rec_found==1 and zoomer.loginid or 'anonymous')
        else:
            zoomer.user_groups = zoomer.acl.get_groups(zoomer.loginid)
            zoomer.user_id = zoomer.acl.get_user_id(zoomer.loginid)
            import dzutil
            dzutil.me = zoomer.user_id

        zoomer.trace('grabbed user groups %s' % repr(zoomer.user_groups))
        zoomer.trace('user_id %s' % repr(zoomer.user_id))

        # Construct a list of applications that this user can use
        zoomer.appnames = [rec.NAME for rec in zoomer.db("select NAME from dz_products where status in ('D','P','T')")]
        zoomer.user_apps = self.get_user_applications()
        zoomer.system_apps = self.get_system_applications()
#        zoomer.trace('grabbed user applications %s ' % repr(zoomer.user_apps))

        # Decide which application is running
        if zoomer.loginid == anonymous_user:
            zoomer.default_app_name = zoomer.index_app
        else:
            zoomer.default_app_name = zoomer.home_app

        if webvars.app in zoomer.apps:
            app_name = webvars.app
            zoomer.session.app_name = webvars.app
#         elif zoomer.session.app_name in zoomer.apps:
#             app_name = zoomer.session.app_name
        elif zoomer.default_app_name in zoomer.apps:
            app_name = zoomer.default_app_name
            webvars._clear()
        else:
            app_name = 'login'
            webvars._clear()

        # Set some application globals
        zoomer.webvars   = webvars
        zoomer.app_url   = '%s?sid=%s&amp;app=%s' % (zoomer.main_url,zoomer.sid,app_name)

        # Show start time
        zoomer.trace('running application %s' % app_name)
        zoomer.trace(zoomer.webvars)

        # Capture all stdout output
        real_stdout = sys.stdout
        sys.stdout = StringIO()

        # Set theme here so applications can override the current theme in code (rethink this - re: why do apps care?!)
        zoomer.set_theme(zoomer.theme_name)

        try: # Capturing stdout
            try: # Exception handling

                zoomer.app = self.load_app(app_name)

                # switch to the application directory
                chdir(zoomer.app.path)

                # run the application and save the response
                zoomer.app_response = zoomer.app.run()

            except:
                import traceback
                exception = traceback.format_exc()
                zoomer.log.log('E',exception)
                if zoomer.trapping=='2':    ## error mode = to screen
                    zoomer.app_response = ExceptionResponse(exception)
                else:
                    if zoomer.trapping=='1':
                        try:
                            ## bug report submission
                            ##  initially an email, but the desired option
                            ##  is to post to a web service (dz bug tracker)
                            import dzmail
                            dzmail.SendMail(
                                zoomer.support_email,
                                zoomer.support_email,
                                '%s (%s) Error' % (zoomer.name,zoomer.site_url),
                                exception.replace('\n','<br />').replace('  ','&nbsp;&nbsp;'),'html')
                        except: pass
                    zoomer.app_response = HTMLResponse(zoomer.error_page)
        finally:
            zoomer.printed_output = sys.stdout.getvalue()
            sys.stdout = real_stdout

        # Stop message
        zoomer.trace('finished running application')
        zoomer.log.log('C','')

        # Save session variables
        if not zoomer.session.iskill():
            zoomer.session.save_session()
        else:
            zoomer.session.destroy_session()
            zoomer.sid = ''

        # if session id is passed with a cookie, save it now
        if zoomer.use_cookies:
            # Set session cookie
            ck = Cookie.SimpleCookie()
            ck[session_cookie_name] = zoomer.sid
            (k,v) = ck[session_cookie_name].output().split(':',1)
            zoomer.app_response.headers[k] = v
            zoomer.debug('cookie is %s'%v)

        # This response should contain a fully rendered HTTP response
        return zoomer.app_response.render()

    def close(self):
        """Any manager close operations"""
        zoomer.db.close()


if __name__ == '__main__':
    manager = Manager()

    import unittest

    class ManagerTestCase(unittest.TestCase):

        def test_Init(self):
            m = Manager()
            self.assert_(m)

        def test_Load_App(self):
            m = Manager()
            app = m.load_app('hello')
            self.assert_(app)

        def test_Load_User_App(self):
            m = Manager()
            app = m.load_app('go')
            self.assert_(app)

    unittest.main()
